///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_USTORAGE_H_
#define _BUOLA_UTILITY_USTORAGE_H_

#include <buola/buola.h>

namespace buola {

//class for fixed/dynamic storage with move semantics and 0 specialization
//the dynamic version optionally has automatic storage for small capacities
//it optionally holds the size for the dynamic version
//it works only with trivially copiable types
//the dynamic version which doesn't hold the size can only be copied if the size is specified in the constructor
//(no default copy construction)

//fixed size version (array size, store size and allocator don't matter
template<typename tScalar,size_t vCapacity,size_t vArraySize=0,typename tAllocator=std::allocator<tScalar>>
class UStorage
{
public:
    UStorage()=default;

    UStorage(const size_t pCapacity)
    {
        assert(pCapacity==vCapacity);
    }

    UStorage(const UStorage&)=default;

    UStorage(const UStorage &pO,size_t pCapacity)
    {
        assert(pCapacity==vCapacity);
        copy_array(pO.Data(),mData,vCapacity);
    }

    UStorage(const UStorage<tScalar,cDynamic> &pO,size_t pCapacity=vCapacity)
    {
        assert(pCapacity==vCapacity);
        copy_array(pO.Data(),mData,vCapacity);
    }

    UStorage &operator=(const UStorage&)=delete;

    void Assign(const UStorage &pO,size_t pCapacity=vCapacity)
    {
        assert(pCapacity==vCapacity);
        copy_array(pO.Data(),mData,vCapacity);
    }

    void Assign(const UStorage<tScalar,cDynamic> &pO,size_t pCapacity=vCapacity)
    {
        assert(pCapacity==vCapacity);
        copy_array(pO.Data(),mData,vCapacity);
    }

    size_t Capacity() const     {   return vCapacity;   }
    
    tScalar *Data()             {   return mData;       }
    const tScalar *Data() const {   return mData;       }

    tScalar &At(size_t i)       {   return mData[i];}
    tScalar At(size_t i) const  {   return mData[i];}
    
    //doesn't preserve any data
    void Resize(size_t pCapacity,size_t pPreserve=0)
    {
        assert(pCapacity==vCapacity);
    }

private:
    tScalar mData[vCapacity];
};

//zero size version
//it has the dynamic interface to be able to use with the 0*cDynamic size version
template<typename tScalar,size_t vArraySize,typename tAllocator>
class UStorage<tScalar,0,vArraySize,tAllocator>
{
public:
    UStorage()=default;

    UStorage(size_t pCapacity)
    {
        assert(pCapacity==0);
    }

    UStorage(const UStorage&)=default;

    UStorage(const UStorage<tScalar,cDynamic>&,size_t pCapacity)
    {
        assert(pCapacity==0);
    }

    void Assign(const UStorage&)
    {}

    void Assign(const UStorage<tScalar,cDynamic>&,size_t pCapacity)
    {
        assert(pCapacity==0);
    }

    void Resize(size_t pCapacity,size_t pPreserve=0)
    {
        assert(pCapacity==0);
    }

    size_t Capacity() const     {   return 0;           }

    tScalar *Data()             {   return nullptr;     }
    const tScalar *Data() const {   return nullptr;     }

    tScalar &At(size_t)       {   assert(false);      }
    tScalar At(size_t) const  {   assert(false);      }
};

namespace detail {

    template<typename tScalar,typename tAllocator>
    struct UStorageHolder : public tAllocator
    {
        UStorageHolder()
            :   mData(nullptr)
            ,   mCapacity(0)
        {}
        
        UStorageHolder(size_t pCapacity)
            :   mData(this->allocate(pCapacity,nullptr))
            ,   mCapacity(pCapacity)
        {}
        
        tScalar *mData;
        size_t mCapacity;
    };
    
/*namespace detail*/ }

//dynamic size version (0 array size, no store size, no allocator)
template<typename tScalar,typename tAllocator>
class UStorage<tScalar,cDynamic,0,tAllocator>
{
public:
    UStorage()=default;

    explicit UStorage(size_t pSize)
        :   mHolder(pSize)
    {}

    UStorage(const UStorage&)=delete;

    template<size_t vCapacity,size_t vArraySize,typename tOAllocator>
    UStorage(const UStorage<tScalar,vCapacity,vArraySize,tOAllocator> &pO,size_t pCapacity)
        :   mHolder(pCapacity)
    {
        copy_array(pO.Data(),mHolder.mData,pCapacity);
    }

    UStorage(UStorage &&pO)
        :   mHolder(pO.mHolder)
    {
        pO.mHolder.mData=nullptr;
        pO.mHolder.mCapacity=0;
    }

    ~UStorage()
    {
        mHolder.deallocate(mHolder.mData,mHolder.mCapacity);
    }

    UStorage &operator=(const UStorage&)=delete;

    UStorage &operator=(UStorage &&pRH)
    {
        using std::swap;
        swap(mHolder.mData,pRH.mHolder.mData);
        swap(mHolder.mCapacity,pRH.mHolder.mCapacity);
        return *this;
    }

    template<size_t vCapacity,size_t vArraySize,typename tOAllocator>
    void Assign(const UStorage<tScalar,vCapacity,vArraySize,tOAllocator> &pO,size_t pCapacity)
    {
        mHolder.deallocate(mHolder.mData,mHolder.mCapacity);
        mHolder.mData=mHolder.allocate(pCapacity,nullptr);
        mHolder.mCapacity=pCapacity;
        copy_array(pO.Data(),mHolder.mData,pCapacity);
    }

    void Assign(UStorage &&pO)
    {
        using std::swap;
        swap(mHolder.mData,pO.mHolder.mData);
        swap(mHolder.mCapacity,pO.mHolder.mCapacity);
    }

    //doesn't preserve any data
    void Resize(size_t pCapacity)
    {
        mHolder.deallocate(mHolder.mData,mHolder.mCapacity);
        mHolder.mData=mHolder.allocate(pCapacity,nullptr);
        mHolder.mCapacity=pCapacity;
    }

    void Resize(size_t pCapacity,size_t pPreserve)
    {
        if(!pPreserve)
            Resize(pCapacity);
        tScalar *lOld=mHolder.mData;
        mHolder.mData=mHolder.allocate(pCapacity,nullptr);
        copy_array(lOld,mHolder.mData,pPreserve);
        mHolder.deallocate(lOld,mHolder.mCapacity);
        mHolder.mCapacity=pCapacity;
    }
    
    size_t Capacity() const     {   return mHolder.mCapacity;   }

    tScalar *Data()             {   return mHolder.mData;       }
    const tScalar *Data() const {   return mHolder.mData;       }

    tScalar &At(size_t i)       {   return mHolder.mData[i];}
    tScalar At(size_t i) const  {   return mHolder.mData[i];}

private:
    detail::UStorageHolder<tScalar,tAllocator> mHolder;
};

/*namespace buola*/ }

#endif
